/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xw.repo;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.utils.TextTool;

import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.AlphaType;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import static ohos.miscservices.inputmethod.EditorAttribute.PATTERN_TEXT;
import static ohos.miscservices.inputmethod.EditorAttribute.PATTERN_NUMBER;
import static ohos.miscservices.inputmethod.EditorAttribute.PATTERN_PASSWORD;
import static ohos.miscservices.inputmethod.EditorAttribute.PATTERN_PHONE;
import static ohos.multimodalinput.event.TouchEvent.PRIMARY_POINT_UP;

/**
 * X edit text
 */
public class XEditText extends TextField {
    private final int DEFAULT_PADDING = DensityUtil.dp2px(this.getContext(), 4);
    private String mSeparator; // the separator，default is "".
    private int mClearDrawableTint;
    private int mTogglePwdDrawableTint;
    private int mInteractionPadding; // padding of drawables' interactive rect area.
    private boolean disableClear; // disable the clear drawable.
    private boolean mTogglePwdDrawableEnable; // be able to use togglePwdDrawables.
    private boolean disableEmoji; // disable emoji and some special symbol input.
    private PixelMap mClearDrawable;
    private PixelMap mTogglePwdDrawable;
    private OnXTextChangeListener mXTextChangeListener;
    private OnXFocusChangeListener mXFocusChangeListener;
    private OnClearListener mOnClearListener;
    private final TextObserver mTextWatcher;
    private boolean hasFocused;
    private int[] pattern; // pattern to separate. e.g.: mSeparator = "-", pattern = [3,4,4] -> xxx-xxxx-xxxx
    private int[] intervals; // indexes of separators.
    private boolean hasNoSeparator; // if is true, the same as EditText.
    private boolean isPwdInputType;
    private boolean isPwdShow;
    private PixelMap mBitmap;
    private PixelMap mNullBitmap;
    private int mStart;
    private int  mTop;
    private int mHalfPadding;
    private Paint mBitmapPaint = new Paint();
    private Paint mBitmapPaint2 = new Paint();
    private int mIconSize = DensityUtil.dp2px(getContext(), 20);
    private Element mClearElemet;
    private Element mHideElement;
    private Element mShowElement;
    private PixelMap mHideMap;
    private PixelMap mShowMap;

    public XEditText(Context context) {
        this(context, null);
    }

    public XEditText(Context context, AttrSet attrSet) {
        this(context, attrSet, "");
    }

    public XEditText(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initAttrs(attrSet);
        setFocusChangedListener(new FocusChangedListener() {
            @Override
            public void onFocusChange(Component component, boolean booleanb) {
                hasFocused = booleanb;
                logicOfCompoundDrawables();
//
                if (mXFocusChangeListener != null) {
                    mXFocusChangeListener.onFocusChange(component, booleanb);
                }
            }
        });
        setPadding(10, 10, 10, 10);
        setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                if (!isEnabled()) {
                    return false;
                }
                if (hasFocused && touchEvent.getAction() == PRIMARY_POINT_UP) {
                    LogUtil.info("onTouchEvent", touchEvent.getPointerPosition(0).getX() + "");
                    int dw = mTogglePwdDrawable == null ? 0 : mTogglePwdDrawable.getImageInfo().size.width;
                    float eventX = touchEvent.getPointerPosition(0).getX();
                    if (eventX > getWidth() - getPaddingEnd() - dw) {

                        if (getTextInputType() == PATTERN_TEXT) {
                            mTogglePwdDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_ic_hide).get();
                            if (mHideElement != null) {
                                mTogglePwdDrawable = mHideMap;
                            }
                            Canvas canvas = new Canvas(new Texture(mTogglePwdDrawable));
                            if (mTogglePwdDrawableTint > 0) {
                                canvas.drawColor(mTogglePwdDrawableTint, Canvas.PorterDuffMode.SRC_IN);
                            }
                            PixelMapElement shapeElement = new PixelMapElement(mTogglePwdDrawable);
                            setAroundElements(null, null, shapeElement, null);
                            setTextInputType(PATTERN_PASSWORD);
                        } else {
                            mTogglePwdDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_ic_show).get();
                            if (mShowElement != null) {
                                mTogglePwdDrawable = mShowMap;
                            }
                            Canvas canvas = new Canvas(new Texture(mTogglePwdDrawable));
                            if (mTogglePwdDrawableTint > 0) {
                                canvas.drawColor(mTogglePwdDrawableTint, Canvas.PorterDuffMode.SRC_IN);
                            }
                            PixelMapElement shapeElement = new PixelMapElement(mTogglePwdDrawable);
                            setAroundElements(null, null, shapeElement, null);
                            setTextInputType(PATTERN_TEXT);
                        }
                    }
                    if (!disableClear) {
                        if (eventX > getWidth() - getPaddingEnd() - dw
                                - mClearDrawable.getImageInfo().size.width - mInteractionPadding && eventX < getWidth() - getPaddingEnd() - dw - mInteractionPadding) {
                            if (mOnClearListener != null) {
                                mOnClearListener.onClear();
                            }
                            setText("");
                            return true;
                        }
                    }
                }
                return false;
            }
        });

        mTextWatcher = new MyTextWatcher();
        addTextObserver(mTextWatcher);
    }

    /**
     * Sets on clear listener *
     *
     * @param listener listener
     */
    public void setOnClearListener(OnClearListener listener) {
        mOnClearListener = listener;
    }

    /**
     * Logic of compound drawables
     */
    private void logicOfCompoundDrawables() {
        if (!hasFocused) {
            setCompoundDrawablesCompat();
        } else {
            if (isPwdInputType) {
                if (mTogglePwdDrawableEnable) {
                    if (getTextInputType() == PATTERN_TEXT) {
                        mTogglePwdDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_ic_show).get();
                        if (mShowMap != null) {
                            mTogglePwdDrawable = mShowMap;
                        }
                        Canvas canvas = new Canvas(new Texture(mTogglePwdDrawable));
                        if (mTogglePwdDrawableTint > 0) {
                            canvas.drawColor(mTogglePwdDrawableTint, Canvas.PorterDuffMode.SRC_IN);
                        }
                        PixelMapElement shapeElement = new PixelMapElement(mTogglePwdDrawable);

                        setAroundElements(null, null, shapeElement, null);
                    } else {
                        mTogglePwdDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_ic_hide).get();
                        if (mHideMap != null) {
                            mTogglePwdDrawable = mHideMap;
                        }
                        Canvas canvas = new Canvas(new Texture(mTogglePwdDrawable));
                        if (mTogglePwdDrawableTint > 0) {
                            canvas.drawColor(mTogglePwdDrawableTint, Canvas.PorterDuffMode.SRC_IN);
                        }
                        PixelMapElement shapeElement = new PixelMapElement(mTogglePwdDrawable);
                        setAroundElements(null, null, shapeElement, null);
                    }
                }
            }
            if (mTogglePwdDrawable == null) {
                if (!isTextEmpty()) {
                    if (mClearElemet != null) {
                        mClearDrawable = generateIconBitmaps(mClearElemet, mIconSize, mIconSize);
                    }
                    PixelMapElement shapeElement = new PixelMapElement(mClearDrawable);
                    setAroundElements(null, null, shapeElement, null);
                }
            } else {
                draw();
            }
        }
    }

    private void draw() {
        addDrawTask(new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                if (!disableClear && mClearDrawable != null && !isTextEmpty()) {
                    mBitmap = mClearDrawable;
                    int toggleImageWidth = mTogglePwdDrawable == null ?
                            0 : mTogglePwdDrawable.getImageInfo().size.width;
                    if (isRtl()) {
                        if (mStart * mTop == 0) {
                            mStart = getPaddingEnd() + toggleImageWidth
                                    + mInteractionPadding;
                            mTop = (getHeight()) >> 1;
                        }
                    } else {
                        if (mStart * mTop == 0) {

                            mStart = getWidth() - getPaddingEnd() - toggleImageWidth
                                    - mClearDrawable.getImageInfo().size.width - mInteractionPadding;
                            mTop = (getHeight() - mBitmap.getImageInfo().size.height) >> 1;

                        }
                    }
                    canvas.drawPixelMapHolder(new PixelMapHolder(mBitmap), mStart, mTop, mBitmapPaint2);
                }
            }
        });
    }

    private void setCompoundDrawablesCompat() {
        setAroundElements(null, null, null, null);
        addDrawTask(new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                canvas.drawPixelMapHolder(new PixelMapHolder(mNullBitmap), mStart, 0, mBitmapPaint2);
            }
        });
    }

    private void initAttrs(AttrSet attrSet) {
        initField(attrSet);
        String xPattern = AttrUtils.getStringFromAttr(attrSet, "x_pattern", "");

        isPwdInputType = (getTextInputType() == PATTERN_PASSWORD);
        if (mSeparator == null) {
            mSeparator = "";
        }
        hasNoSeparator = TextTool.isNullOrEmpty(mSeparator);
        if (mSeparator.length() > 0) {
            int inputType = getTextInputType();
            if (inputType == PATTERN_NUMBER) {
                // If the inputType is number, the separator can't be inserted, so change to phone type.
                setTextInputType(PATTERN_PHONE);
            }
        }
        if (mInteractionPadding < 0){
            mInteractionPadding = 0;
        }

        mHalfPadding = mInteractionPadding >> 1;
        if (!disableClear) {
            mClearDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_met_ic_clear).get();
            mClearElemet = AttrUtils.getElementFromAttr(attrSet, "x_clearDrawable", null);
            if (mClearElemet != null) {
                mClearDrawable = generateIconBitmaps(mClearElemet, mIconSize, mIconSize);
            }
            mHideElement = AttrUtils.getElementFromAttr(attrSet, "x_hidePwdDrawable", null);
            mShowElement = AttrUtils.getElementFromAttr(attrSet, "x_showPwdDrawable", null);

            if (mHideElement != null) {
                mHideMap = generateIconBitmaps(mHideElement, mIconSize, mIconSize);
            }
            if (mShowElement != null) {
                mShowMap = generateIconBitmaps(mShowElement, mIconSize, mIconSize);
            }
            Canvas canvas = new Canvas(new Texture(mClearDrawable));
            if (mClearDrawableTint > 0) {
                canvas.drawColor(mClearDrawableTint, Canvas.PorterDuffMode.SRC_IN);
            }
        }
        dealWithInputTypes(true);
        dealPettern(xPattern);
    }

    private void dealPettern(String xPattern) {
        if (!mSeparator.isEmpty() && !isPwdInputType) {
            boolean ok = true;
            if (xPattern.contains(",")) {
                String[] split = xPattern.split(",");

                int[] array = new int[split.length];
                for (int i = 0; i < array.length; i++) {
                    try {
                        array[i] = Integer.parseInt(split[i]);
                    } catch (Exception e) {
                        ok = false;
                        break;
                    }
                }
                if (ok) {
                    setPattern(array, mSeparator);
                }
            } else {
                try {
                    setPattern(new int[]{4, 4, 4, 4}, mSeparator);
                } catch (Exception e) {
                    ok = false;
                }
            }
        }
    }

    private void initField(AttrSet attrSet) {
        mSeparator = AttrUtils.getStringFromAttr(attrSet, "x_separator", "");
        disableClear = AttrUtils.getBooleanFromAttr(attrSet, "x_disableClear", false);
        mTogglePwdDrawableEnable = AttrUtils.getBooleanFromAttr(attrSet, "x_togglePwdDrawableEnable", true);
        mClearDrawableTint = AttrUtils.getColorFromAttr(attrSet, "x_clearDrawableTint", 0);
        mTogglePwdDrawableTint = AttrUtils.getColorFromAttr(attrSet, "x_togglePwdDrawableTint", 0);
        disableEmoji = AttrUtils.getBooleanFromAttr(attrSet, "x_disableEmoji", false);
        mInteractionPadding = AttrUtils.getDimensionFromAttr(attrSet, "x_interactionPadding", DEFAULT_PADDING);
    }

    private PixelMap generateIconBitmaps(Element drawable, int width, int height) {
        if (drawable == null){
            return null;
        }

        PixelMap bitmap = drawableToPixelmap(drawable);
        Canvas canvas = new Canvas(new Texture(bitmap));
        drawable.setBounds(0, 0, bitmap.getImageInfo().size.width, bitmap.getImageInfo().size.height);
        drawable.drawToCanvas(canvas);
        return generateIconBitmaps(copy(bitmap, width, height));
    }

    private PixelMap generateIconBitmaps(PixelMap origin) {
        if (origin == null) {
            return null;
        }
        PixelMap iconBitmap;
        origin = scaleIcon(origin);
        iconBitmap = copy(origin, origin.getImageInfo().size.width, origin.getImageInfo().size.height);
        return iconBitmap;
    }

    /**
     * Scale icon pixel map
     *
     * @param origin origin
     * @return the pixel map
     */
    private PixelMap scaleIcon(PixelMap origin) {
        int width = origin.getImageInfo().size.width;
        int height = origin.getImageInfo().size.height;
        int size = Math.max(width, height);
        if (size == mIconSize) {
            return origin;
        } else if (size > mIconSize) {
            int scaledWidth;
            int scaledHeight;
            if (width > mIconSize) {
                scaledWidth = mIconSize;
                scaledHeight = (int) (mIconSize * ((float) height / width));
            } else {
                scaledHeight = mIconSize;
                scaledWidth = (int) (mIconSize * ((float) width / height));
            }
            return copy(origin, scaledWidth, scaledHeight);
        } else {
            return origin;
        }
    }

    /**
     * Copy pixel map
     *
     * @param source source
     * @param width  width
     * @param height height
     * @return the pixel map
     */
    public PixelMap copy(PixelMap source, int width, int height) {
        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
        initializationOptions.alphaType = AlphaType.PREMUL;
        initializationOptions.size = new Size(width, height);
        return PixelMap.create(source, initializationOptions);
    }

    /**
     * Drawable to pixelmap pixel map
     *
     * @param drawable drawable
     * @return the pixel map
     */
    public static PixelMap drawableToPixelmap(Element drawable) {
        if (drawable instanceof Element) {
            return ((PixelMapElement) drawable).getPixelMap();
        }
        return null;
    }

    /**
     * Is text empty boolean
     *
     * @return the boolean
     */
    private boolean isTextEmpty() {
        return getText().trim().length() == 0;
    }

    /**
     * Sets on x text change listener *
     *
     * @param listener listener
     */
    public void setOnXTextChangeListener(OnXTextChangeListener listener) {
        this.mXTextChangeListener = listener;
    }

    /**
     * OnXTextChangeListener is to XEditText what OnTextChangeListener is to EditText.
     */
    public interface OnXTextChangeListener {
        void beforeTextChanged(CharSequence CharSequences, int start, int count, int after);
        void onTextChanged(CharSequence CharSequences, int start, int before, int count);
        void afterTextChanged(String string);
    }

    /**
     * OnXFocusChangeListener is to XEditText what OnFocusChangeListener is to EditText.
     */
    public interface OnXFocusChangeListener {
        void onFocusChange(Component component, boolean hasFocus);
    }

    /**
     * Interface definition for a callback to be invoked when the clear drawable is clicked.
     */
    public interface OnClearListener {
        void onClear();
    }

    /**
     * Sets text input type *
     *
     * @param inputType input type
     */
    @Override
    public void setTextInputType(int inputType) {
        super.setTextInputType(inputType);
        dealWithInputTypes(false);
    }

    /**
     * Deal with input types *
     *
     * @param fromXml from xml
     */
    private void dealWithInputTypes(boolean fromXml) {
        int inputType = getTextInputType();
        if (!fromXml) {
            if (inputType == PATTERN_NUMBER) {
                inputType++;
            }
        }

        if (isPwdInputType) {
            isPwdShow = (inputType != PATTERN_PASSWORD); // textVisiblePassword
            if (isPwdShow) {
                mTogglePwdDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_ic_show).get();
            } else {
                mTogglePwdDrawable = ResUtil.getPixelMap(getContext(), ResourceTable.Media_ic_hide).get();
            }
        }


    }

    /**
     * Set custom pattern.
     *
     * @param pattern   e.g. pattern:{4,4,4,4}, separator:"-" to xxxx-xxxx-xxxx-xxxx
     * @param separator separator
     */
    public void setPattern(int[] pattern, String separator) {
        setSeparator(separator);
        setPattern(pattern);

    }

    /**
     * Set custom pattern.
     *
     * @param pattern e.g. pattern:{4,4,4,4}, separator:"-" to xxxx-xxxx-xxxx-xxxx
     */
    public void setPattern(int[] pattern) {
        this.pattern = pattern;
        intervals = new int[pattern.length];
        int sum = 0;
        for (int i = 0; i < pattern.length; i++) {
            sum += pattern[i];
            intervals[i] = sum;
        }

    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        logicOfCompoundDrawables();
    }

    /**
     * Sets separator *
     *
     * @param separator separator
     */
    public void setSeparator(String separator) {
        if (mSeparator.equals(separator)){
            return;
        }
        mSeparator = separator;
        hasNoSeparator = TextTool.isNullOrEmpty(mSeparator);
    }

    // =========================== MyTextWatcher ================================
    private class MyTextWatcher implements Text.TextObserver {
        @Override
        public void onTextUpdated(String string, int var2, int var3, int var4) {
            try {
                logicOfCompoundDrawables();
            } catch (Exception e) {
                LogUtil.info("tag","Exception");
            }
            if (disableEmoji) {
                for (int j = 0; j < string.length(); j++) {
                    int type = Character.getType(string.charAt(j));
                    if (type == Character.SURROGATE || type == Character.OTHER_SYMBOL) {
                        setText("");
                        return;
                    }
                }
            }
            if (mSeparator.isEmpty()) {
                if (mXTextChangeListener != null) {
                    mXTextChangeListener.afterTextChanged(string);
                }
                return;
            }
            String trimmedText;
            if (hasNoSeparator) {
                trimmedText = string.toString().trim();
            } else {
                trimmedText = string.toString().replaceAll(mSeparator, "").trim();
            }
            setTextToSeparate(trimmedText, false);

            if (mXTextChangeListener != null) {
                mXTextChangeListener.afterTextChanged(string);
            }
        }
    }

    /**
     * Gets text trimmed *
     *
     * @return the text trimmed
     */
    public String getTextTrimmed() {
        return getTextEx().trim();
    }

    /**
     * Gets text ex *
     *
     * @return the text ex
     */
    public String getTextEx() {
        if (hasNoSeparator) {
            return getTextNoneNull();
        } else {
            return getTextNoneNull().replaceAll(mSeparator, "");
        }
    }

    /**
     * Gets text none null *
     *
     * @return the text none null
     */
    private String getTextNoneNull() {
        String editable = getText();
        return editable == null ? "" : editable;
    }

    /**
     * Sets text ex *
     *
     * @param text text
     */
    public void setTextEx(String text) {
        if (TextTool.isNullOrEmpty(text) || hasNoSeparator) {
            setText(text);
        } else {
            setTextToSeparate(text, true);
        }
    }

    /**
     * Sets text to separate *
     *
     * @param charc        charc
     * @param fromUser from user
     */
    private void setTextToSeparate(CharSequence charc, boolean fromUser) {
        if (charc.length() == 0 || intervals == null) {
            return;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0, length1 = charc.length(); i < length1; i++) {
            builder.append(charc.subSequence(i, i + 1));
            for (int j = 0, length2 = intervals.length; j < length2; j++) {
                if (i == intervals[j] && j < length2 - 1) {
                    builder.insert(builder.length() - 1, mSeparator);
                }
            }
        }

        String text = builder.toString();
        setText(text);
    }

    /**
     * Sets toggle pwd drawable enable *
     *
     * @param enable enable
     */
    public void setTogglePwdDrawableEnable(boolean enable) {
        if (mTogglePwdDrawableEnable == enable){
            return;
        }
        mTogglePwdDrawableEnable = enable;
        dealWithInputTypes(false);
    }

    /**
     * Sets disable emoji *
     *
     * @param disableEmoji disable emoji
     */
    public void setDisableEmoji(boolean disableEmoji) {
        if (this.disableEmoji == disableEmoji){
            return;
        }
        this.disableEmoji = disableEmoji;
    }

    /**
     * Sets on x focus change listener *
     *
     * @param listener listener
     */
    public void setOnXFocusChangeListener(OnXFocusChangeListener listener) {
        mXFocusChangeListener = listener;
    }
}
